/**
 *  The Atlas Education Kit Sample Application
 *  
 *    A GUI for viewing the data streams from two sensors
 *    (an analog pressure sensor and a digital contact 
 *    sensor) and for controlling the position of a servo.
 *   
 *  Application Implementation class
 *    (will be instantiated by Activator class)
 *  
 *  Jeff King
 *  support@pervasa.com
 *  
 *  March 6, 2007
 */
package com.pervasa.demo.re.impl;

// awt/swing GUI components
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import javax.swing.JFrame;
import javax.swing.JOptionPane;

import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.sensorplatform.actuators.servo.hs322.HS322Servo;
import org.sensorplatform.sensors.digitalcontact.DigitalContactSensor;
import org.sensorplatform.sensors.humidity.HumiditySensor;
import org.sensorplatform.sensors.pressure.InterlinkPressureSensor;
import org.sensorplatform.sensors.temperature.TemperatureSensor;

import com.pervasa.atlas.dev.service.AtlasClient;
import com.pervasa.atlas.dev.service.AtlasService;
import com.pervasa.demo.re.impl.command.Executor;
import com.pervasa.demo.re.impl.command.Parser;
import com.pervasa.demo.re.impl.command.SyntaxException;
import com.pervasa.demo.re.impl.data.Action;
import com.pervasa.demo.re.impl.data.Condition;
import com.pervasa.demo.re.impl.data.Event;
import com.pervasa.demo.re.impl.data.Rule;
import com.pervasa.demo.re.impl.data.TFM;

// the AtlasClient interface is for applications that want to be able
//   to access services provided by the Atlas platform
// JFrame is just a base class for Java GUIs. If you're creating an
//   Atlas application that doesn't have a GUI, you don't need to
//   extend JFrame
@SuppressWarnings("serial")
public class ReactivityEngine extends JFrame implements AtlasClient {

	private Executor executor;
	private Parser parser;

	private javax.swing.JButton jButton1;
	private javax.swing.JLabel jLabel1;
	private javax.swing.JLabel jLabel2;
	private javax.swing.JScrollPane jScrollPane1;
	private javax.swing.JScrollPane jScrollPane2;
	private javax.swing.JTextArea jTextArea1;
	private javax.swing.JTextArea jTextArea2;
	int run = 0;

	// ameya declared maps to use here
	Map<String, String> basicEvents = new HashMap<String, String>();
	Map<String, Event> eventList = new ConcurrentHashMap<String, Event>();

	// define the condition map
	Map<String, Condition> runtimeConditions = new ConcurrentHashMap<String, Condition>();

	// define the actions map
	Map<String, Action> runtimeActions = new ConcurrentHashMap<String, Action>();

	// define the basic actions map
	Map<String, String> basicActions = new HashMap<String, String>();

	// define the rule map
	Map<String, Rule> rules = new HashMap<String, Rule>();

	// NEW: add two map to represent the hashmap of node-id with sensor and servref with sensor
	Map<String, AtlasService> sensorMap = new HashMap<String, AtlasService>();
	Map<AtlasService, ServiceReference> sensorRefMap = new HashMap<AtlasService, ServiceReference>();
	Map<AtlasService, ArrayList<Event>> sensorListenerMap = new HashMap<AtlasService, ArrayList<Event>>();
	
	// NEW: add two map to represent the hashmap of node-id with actuator and servref with actuator
	Map<String, AtlasService> actuatorMap = new HashMap<String, AtlasService>();
	Map<AtlasService, ServiceReference> actuatorRefMap = new HashMap<AtlasService, ServiceReference>();
	
	// NEW: TFM Map
	Map<String, TFM> tfmMap = new HashMap<String, TFM>();
	
	public Map<String, TFM> getTfmMap() {
		return tfmMap;
	}

	public Map<String, AtlasService> getActuatorMap() {
		return actuatorMap;
	}

	public Map<AtlasService, ServiceReference> getActuatorRefMap() {
		return actuatorRefMap;
	}

	// ameya:test variable here
	public boolean filled = false;

	// lock variable
	public Object lock;
	protected javax.swing.JTextArea jTextArea3;
	private javax.swing.JScrollPane jScrollPane3;
	private javax.swing.JLabel jLabel3;

	protected boolean isSwitchOn;

	// KitSampleApp constructor
	// will be called by bundle's Activator class when started in Knopflerfish
	public ReactivityEngine(BundleContext context) {
		isSwitchOn = false;
		this.setVisible(true);
		initGUI();

		executor = new Executor(this, basicEvents, eventList,
				runtimeConditions, runtimeActions, rules, basicActions,
				run, jTextArea2);
	}

	// this method generates the basic GUI for the application bundle
	// it also includes the example of actuator control
	protected void initGUI() {

		/*
		 * try { UIManager.setLookAndFeel(new
		 * SubstanceBusinessBlackSteelLookAndFeel()); } catch (Exception e) {
		 * System.out.println("Substance Raven Graphite failed to initialize");
		 * 
		 * }
		 */

		jLabel1 = new javax.swing.JLabel();
		jScrollPane1 = new javax.swing.JScrollPane();
		jTextArea1 = new javax.swing.JTextArea();
		jScrollPane2 = new javax.swing.JScrollPane();
		jTextArea2 = new javax.swing.JTextArea();
		jButton1 = new javax.swing.JButton();
		jLabel2 = new javax.swing.JLabel();
		jScrollPane3 = new javax.swing.JScrollPane();
		jTextArea3 = new javax.swing.JTextArea();
		jLabel3 = new javax.swing.JLabel();

		setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
		setMinimumSize(new java.awt.Dimension(800, 600));
		addComponentListener(new java.awt.event.ComponentAdapter() {
		});
		getContentPane().setLayout(null);
		getContentPane().add(jLabel1);
		jLabel1.setBounds(90, 140, 150, 40);

		jScrollPane1
				.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);

		jTextArea1.setColumns(20);
		jTextArea1.setLineWrap(true);
		jTextArea1.setRows(1);
		jTextArea1.setAutoscrolls(false);
		jTextArea1.addKeyListener(new java.awt.event.KeyAdapter() {
			public void keyPressed(java.awt.event.KeyEvent evt) {
				jTextArea1KeyPressed(evt);
			}
		});
		jScrollPane1.setViewportView(jTextArea1);

		getContentPane().add(jScrollPane1);
		jScrollPane1.setBounds(210, 104, 360, 24);

		jTextArea2.setColumns(20);
		jTextArea2.setRows(5);
		jTextArea2.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1,
				1, 1));
		jTextArea2.setRequestFocusEnabled(false);
		jScrollPane2.setViewportView(jTextArea2);

		getContentPane().add(jScrollPane2);
		jScrollPane2.setBounds(80, 210, 630, 260);

		jButton1.setText("OK");
		jButton1.addActionListener(new java.awt.event.ActionListener() {
			public void actionPerformed(java.awt.event.ActionEvent evt) {
				jButton1ActionPerformed(evt);
			}
		});
		getContentPane().add(jButton1);
		jButton1.setBounds(600, 100, 110, 30);

		jLabel2.setFont(new java.awt.Font("Trebuchet MS", 0, 24));
		jLabel2.setText("REACTIVE ENGINE");
		getContentPane().add(jLabel2);
		jLabel2.setBounds(320, 20, 210, 30);

		jScrollPane3
				.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);

		jTextArea3.setColumns(20);
		jTextArea3.setRows(1);
		jTextArea3.setBorder(null);
		/*
		 * jTextArea3.addKeyListener(new java.awt.event.KeyAdapter() { public
		 * void keyPressed(java.awt.event.KeyEvent evt) {
		 * jTextArea3KeyPressed(evt); } });
		 */
		jScrollPane3.setViewportView(jTextArea3);

		getContentPane().add(jScrollPane3);
		jScrollPane3.setBounds(80, 190, 630, 20);

		jLabel3.setText("Enter the Command");
		getContentPane().add(jLabel3);
		jLabel3.setBounds(90, 100, 150, 40);

		pack();

	}

	private void jTextArea1KeyPressed(java.awt.event.KeyEvent evt) {
		if (evt.getKeyChar() == '\n') {
			String str = jTextArea1.getText();
			System.out.println("BEFORE PARSE+" + str);
			parse(str);

			jTextArea3.setText(">" + str);
			jTextArea1.setText("");

		}
	}

	private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
		String str = jTextArea1.getText();
		parse(str);
		String str1 = jTextArea1.getText();
		jTextArea3.setText(">" + str1);
		jTextArea1.setText("");
	}

	// this method is called by the KitSampleApp bundle's Activator when any
	// service running
	// in Knopflerfish starts or changes
	// sref is the Knopflerfish ServiceReference ID of the new/changed service
	// dev is a direct reference to the new/changed service
	// AtlasService is an interface implemented by all Atlas sensor and actuator
	// services, and is what allows an AtlasClient implementor (like
	// KitSampleApp)
	// to bind with attached Atlas devices.
	public void addDevice(ServiceReference sref, AtlasService dev) {
		// if the pressure sensor service comes online, grab a reference to it,
		// subscribe to its data stream, and update the pressure sensor
		// service availability icon
		if (dev instanceof InterlinkPressureSensor) {
			((InterlinkPressureSensor) dev).subscribeToPressureData(this);
			// NEW
			sensorMap.put(sref.getProperty("Node-Id").toString(), dev);
			sensorRefMap.put(dev, sref);
			sensorListenerMap.put(dev, new ArrayList<Event>());
			
			// ameya added function to put this sensor and range into hashmap
			String nodeId = sref.getProperty("Node-Id").toString();
			addToBasicMap(nodeId, "Pressure", "[0,1000]");
		}
		// if the servo service comes online, grab a reference to it and
		// update the availability icon
		else if (dev instanceof HS322Servo) {
			// NEW
			actuatorMap.put(sref.getProperty("Node-Id").toString(), dev);
			actuatorRefMap.put(dev, sref);
			
			// ameya added action for this node in actions map
			String nodeId = sref.getProperty("Node-Id").toString();
			addToBasicActions(nodeId, "Servo", "[0,180]");
		}
		// if the digital contact sensor service comes online, grab a reference
		// to it,
		// subscribe to its data stream, and update the availability icon
		else if (dev instanceof DigitalContactSensor) {
			((DigitalContactSensor) dev).subscribeToContactData(this);
			// NEW
			sensorMap.put(sref.getProperty("Node-Id").toString(), dev);
			sensorRefMap.put(dev, sref);
			sensorListenerMap.put(dev, new ArrayList<Event>());
			
			// ameya added function to put this sensor and range into hashmap
			String nodeId = sref.getProperty("Node-Id").toString();
			addToBasicMap(nodeId, "Contact", "[0,1]");
		}

		// ameya to detect temp sensors
		else if (dev instanceof TemperatureSensor) {
			((TemperatureSensor) dev).subscribeToSensorData(this);
			// NEW
			sensorMap.put(sref.getProperty("Node-Id").toString(), dev);
			sensorRefMap.put(dev, sref);
			sensorListenerMap.put(dev, new ArrayList<Event>());
						
			System.out.println("Got a temp sensor");
			String nodeId = sref.getProperty("Node-Id").toString();
			addToBasicMap(nodeId, "Temperature", "[-100,300]");
		}
		// ameya to detect temp sensors
		else if (dev instanceof HumiditySensor) {
			((HumiditySensor) dev).subscribeToSensorData(this);
			// NEW
			sensorMap.put(sref.getProperty("Node-Id").toString(), dev);
			sensorRefMap.put(dev, sref);
			sensorListenerMap.put(dev, new ArrayList<Event>());
						
			System.out.println("Got a humid sensor");
			String nodeId = sref.getProperty("Node-Id").toString();
			addToBasicMap(nodeId, "Humidity", "[0,100]");
		}
		
	}

	// this method is called by the KitSampleApp bundle's Activator when any
	// service
	// running in Knopflerfish goes offline
	// sref is the Knopflerfish ServiceReference ID of the departing service
	// because the service has already been unbound in OSGi, we cannot do the
	// "instanceof" check like in addDevice (the "dev" parameter in that
	// method would be null here). This is why addDevice must record the
	// ServiceReference.
	public void removeDevice(ServiceReference sref) {
		AtlasService dev = null;
		dev = sensorMap.get(sref.getProperty("Node-Id").toString());
		if (dev != null) {
			sensorMap.remove(sref.getProperty("Node-Id").toString());
			sensorRefMap.remove(dev);
			sensorListenerMap.remove(dev);
		}
		else {
			dev = actuatorMap.get(sref.getProperty("Node-Id").toString());
			actuatorMap.remove(sref.getProperty("Node-Id").toString());
			actuatorRefMap.remove(dev);
		}
		dev = null;
				
	}

	// this is basically a callback method called by any Atlas service
	// bundle to which the application has subscribed (in this case,
	// the pressure sensor service and the digital contact sensor
	// service).
	// data contains the reading produced by the sensor
	// props contains information about the sensor that produced the data
	// (name, label, channel to which the device is connected, etc.
	public void ReceivedData(String data, Properties props) {
		// write the received data to the Knopflerfish console
		// this can get rather busy as the two sensors rapidly stream
		// data, so this is commented out by default
		// System.out.println("Received data: " + data);
		// System.out.println("AMEYA" + props.toString() + "Data :" + data);

		String sensorMeasure = new String("Unknown");
		String[] keysarr = new String[props.size()];
		props.keySet().toArray(keysarr);

		// the sensor service bundles are configured so that the
		// "measure-type" property will identify the sensor for this application
		if (props.containsKey("measure-type"))
			sensorMeasure = props.getProperty("measure-type");
		int sensorReading = Integer.parseInt(data);

		// ameya: inserted code here to update event values on receiving this
		// data
		String nodeId = props.getProperty("Node-Id");
		// update the node value in the nodeValues. nodeValues is a Map which
		// stores all the values of the nodes online now.
		// a value is updated every time data is received
//		if (nodeValues.containsKey(nodeId)) {
//			nodeValues.remove(nodeId);
//		}
//		nodeValues.put(nodeId, data.toString());

		// System.out.println("Current node values are here:");
		// showMap(nodeValues);

		// check and update the events that may have changed. actually a new
		// thread can be spawned to do this. need to check feasibility of this
		// also if performance takes a hit, we might need to implementing a
		// queue. need not be serialized cause this recvdData method will
		// write to the queue and our thread will read from it.
		if (run == 1) {
			// evaluate and trigger any rules
			System.out.println(nodeId + ":" + data);
			for (Event e : sensorListenerMap.get(sensorMap.get(nodeId))) {
				System.out.println(e.getName() + " is being evaluated.");
				e.evaluate(Integer.parseInt(data));
			}
		}

	}

	public int getRun() {
		return run;
	}

	public void setRun(int run) {
		this.run = run;
	}

	public Map<AtlasService, ArrayList<Event>> getSensorListenerMap() {
		return sensorListenerMap;
	}

	public Map<String, AtlasService> getSensorMap() {
		return sensorMap;
	}

	public Map<AtlasService, ServiceReference> getSenserRefMap() {
		return sensorRefMap;
	}

	// ameya: added functions here for custom processing

	public Map<String, Condition> getRuntimeConditions() {
		return runtimeConditions;
	}

	public Map<String, Action> getRuntimeActions() {
		return runtimeActions;
	}

	public void addToBasicMap(String nodeid, String type, String range) {
		basicEvents.put(nodeid, nodeid + "," + type + "," + range);
	}

	public void addToBasicActions(String nodeid, String type, String range) {
		basicActions.put(nodeid, "Move Servo " + nodeid + ", " + range);
	}

	public Map<String, Event> getEventList() {
		return eventList;
	}

	public void parse(String str) {

		parser = new Parser(str, this, executor);
		try {
			parser.parse();
		} catch (SyntaxException e) {
			JOptionPane.showMessageDialog(this, "Unexpected token" + e.toString());
		}

	}
}
